package gu.sql2java.manager;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static gu.sql2java.SimpleLog.log;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

import gu.sql2java.Constant;
import gu.sql2java.IDataSourceConfig;
import gu.sql2java.ListenerContainer;
import gu.sql2java.Constant.JdbcProperty;
import gu.sql2java.ListenerContainer.FireType;

/**
 * The datasource configuration 
 * @author guyadong
 */
public final class DataSourceConfig implements Constant,IDataSourceConfig
{
	static final String DT_SQLITE = "SQLITE";
	static final String DT_C3P0 = "C3P0";
	static final String DT_DRUID = "DRUID";
	private static boolean debugOutput = false;
	/** JDBC properties from default properties file */
	private static volatile Properties databaseProperties;

	final String jdbcDriver;
	final String jdbcUrl;
	final String jdbcUsername;
	final String jdbcPassword;
	final boolean isDebug;
	final String alias;
	final FireType fireType;
	final String dataSource;
	volatile String generatedkeyRetrieve = null;
	volatile String generatedkeyStatement = null;
	volatile PageQueryImplType pageQueryImplType = null;
	volatile Boolean supportInsertValues;
	private final Properties initProperties;
	DataSourceConfig()
	{
		this(asInitProps(getDatabaseproperties()));
	}

	/**
	 * @param properties the properties object to be used
	 */
	private DataSourceConfig(Properties properties){
		alias = firstNonNull(properties.getProperty(JdbcProperty.ALIAS.key),DEFAULT_ALIAS);
		fireType = FireType.valueOrNull(properties.getProperty(JdbcProperty.FIRE_TYPE.key));
		isDebug = Boolean.valueOf(properties.getProperty(JdbcProperty.DEBUG.key));
		String prefix= isDebug ? "debug." : "work.";
		jdbcDriver = properties.getProperty(JdbcProperty.JDBC_DRIVER.withPrefix(prefix));
		jdbcUrl = properties.getProperty(JdbcProperty.JDBC_URL.withPrefix(prefix));
		jdbcUsername = properties.getProperty(JdbcProperty.JDBC_USERNAME.withPrefix(prefix));
		jdbcPassword = properties.getProperty(JdbcProperty.JDBC_PASSWORD.withPrefix(prefix));

		generatedkeyRetrieve = properties.getProperty(JdbcProperty.GENERATEDKEY_RETRIEVE.key);
		generatedkeyStatement = properties.getProperty(JdbcProperty.GENERATEDKEY_STATEMENT.key);

		dataSource = properties.getProperty(JdbcProperty.DATASOURCE.key,DT_C3P0).toUpperCase();

		this.initProperties = new Properties();
		this.initProperties.putAll(properties);
		this.initProperties.setProperty(JdbcProperty.JDBC_DRIVER.key, jdbcDriver);
		this.initProperties.setProperty(JdbcProperty.JDBC_URL.key, jdbcUrl);
		this.initProperties.setProperty(JdbcProperty.JDBC_USERNAME.key, jdbcUsername);
		this.initProperties.setProperty(JdbcProperty.JDBC_PASSWORD.key, jdbcPassword);
		if(DT_C3P0.equals(dataSource)){
			String maxPoolSize = properties.getProperty(JdbcProperty.C3P0_MAXPOOLSIZE.withPrefix(prefix));
			String minPoolSize = properties.getProperty(JdbcProperty.C3P0_MINPOOLSIZE.withPrefix(prefix));
			String maxIdleTime = properties.getProperty(JdbcProperty.C3P0_MAXIDLETIME.withPrefix(prefix));
			String idleConnectionTestPeriod = properties.getProperty(JdbcProperty.C3P0_IDLECONNECTIONTESTPERIOD.withPrefix(prefix));
			this.initProperties.setProperty(JdbcProperty.C3P0_MAXPOOLSIZE.key, maxPoolSize);
			this.initProperties.setProperty(JdbcProperty.C3P0_MINPOOLSIZE.key, minPoolSize);
			this.initProperties.setProperty(JdbcProperty.C3P0_MAXIDLETIME.key, maxIdleTime);
			this.initProperties.setProperty(JdbcProperty.C3P0_IDLECONNECTIONTESTPERIOD.key, idleConnectionTestPeriod);
		}
		Arrays.asList(JdbcProperty.values()).forEach(p->{
			initProperties.remove(p.withPrefix(prefix));
		});
		this.initProperties.stringPropertyNames().forEach(k->{
			if(k.startsWith(prefix)){
				/** rename key ,without prefix */
				Object value = initProperties.remove(k);
				initProperties.put(k.substring(prefix.length()), value);
			}
			/** remove unused property*/
			if(isDebug){
				if(k.startsWith("work.")){
					initProperties.remove(k);
				}
			}else if(k.startsWith("debug.")){
				initProperties.remove(k);
			}			
		});
	}
	@Override
	public String getJdbcDriver() {
		return jdbcDriver;
	}
	@Override
	public String getJdbcUrl() {
		return jdbcUrl;
	}
	@Override
	public String getJdbcUsername() {
		return jdbcUsername;
	}
	@Override
	public String getJdbcPassword() {
		return jdbcPassword;
	}
	String getGeneratedkeyRetrieve() {
		return generatedkeyRetrieve;
	}
	String getGeneratedkeyStatement() {
		return generatedkeyStatement;
	}
	public String getDataSource() {
		return dataSource;
	}
	boolean isDebug() {
		return isDebug;
	}
	String getAlias() {
		return alias;
	}
	public Properties getInitProperties() {
		return initProperties;
	}
	
	/**
	 * @param key
	 * @see java.util.Properties#getProperty(java.lang.String)
	 */
	public String getInitProperty(String key) {
		return initProperties.getProperty(key);
	}
	/**
	 * @param key
	 * @param defaultValue
	 * @see java.util.Properties#getProperty(java.lang.String, java.lang.String)
	 */
	public String getInitProperty(String key, String defaultValue) {
		return initProperties.getProperty(key, defaultValue);
	}
	public void logDatabaseProperties(){
    	logDatabaseProperties(this.initProperties, dataSource);
    }
	public static void logDatabaseProperties(Properties props, String datasourceType){
		if(debugOutput){
			log("database using debug environment parameter: ");
			/** 先输出 url,username,password,driverClassName 等关键参数,再输出其他参数 */
			LinkedHashSet<String> allKeys = Sets.newLinkedHashSet(
					Arrays.asList(JdbcProperty.DATASOURCE.key,"url","jdbc.url","username","jdbc.username","password","jdbc.password","driver","jdbc.driver","driverclassname"));
			if(!isNullOrEmpty(datasourceType)){
				log("{}={}",JdbcProperty.DATASOURCE.key,datasourceType);
			}
			/** 参数名自然排序 */
			List<String> sorted = Ordering.natural().sortedCopy(props.stringPropertyNames());
			allKeys.addAll(sorted);
			/** 
			 * 如果输入参数datasourceType不为空或null,
			 * 则忽略 properties中的datasource字段 
			 */
			if(!isNullOrEmpty(datasourceType)){
				allKeys.remove(JdbcProperty.DATASOURCE.key);
			}
			allKeys.forEach(key->{
				if(props.containsKey(key)){
					log("{}={}", key, props.getProperty(key));
				}
			});
		}
	}

	static Properties loadInitProperties(String alias){
		String envVar="SQL2JAVA_CONFIG";
		String propFile="database.properties";
		if(alias != null && !DEFAULT_ALIAS.equals(alias)){
			propFile = alias +"_"+ propFile;
		}
		String confFolder="conf";
		return ConfigUtils.loadAllProperties(propFile, confFolder, envVar, DataSourceConfig.class, false);
	}
	/**
	 * inject properties to {@code target}<br>
	 * be effected only while called before initializing singleton instance 
	 * @param target [out] target properties
	 * @param input input properties
	 */
	static final void injectToProperties(Properties target, EnumMap<JdbcProperty,String> input){
		if(null != target && null != input){        	
			Boolean isDebug = "true".equalsIgnoreCase(input.get(JdbcProperty.DEBUG));
			String prefix = isDebug ? "debug." : "work.";
			JdbcProperty key;
			String value;
			for(Entry<JdbcProperty, String> entry : input.entrySet()){
				key = entry.getKey();
				value = entry.getValue();
				if( null !=value && !value.isEmpty()){
					if(key.isGlobal()){
						target.setProperty(key.key, value);
					}else{
						target.setProperty(key.withPrefix(prefix), value);
					}
				}
			}
		}
	}
	@SuppressWarnings("rawtypes")
	static final EnumMap<JdbcProperty, String> asEnumMap(Map properties, String prefix){
		EnumMap<JdbcProperty, String> enumMap = new EnumMap<JdbcProperty,String>(JdbcProperty.class);
		if(null != properties){
			Object value;
			for(JdbcProperty property : JdbcProperty.values()){
				if(property.isGlobal() || null == prefix){
					value = properties.get(property.key);
				}else{
					value = properties.get(property.withPrefix(prefix));
				}
				if(value instanceof String){
					enumMap.put(property, (String)value);
				}
			}
		}
		return enumMap;
	}
	/**
	 * inject properties to {@link #databaseProperties}<br>
	 * be effected only while called before initializing singleton instance 
	 * @param properties
	 */
	static final void injectProperties(EnumMap<JdbcProperty,String> input){
		injectToProperties(getDatabaseproperties(), input);
	}
	/**
	 * parse string value of {@code property} from properties
	 * @param properties
	 * @param property
	 * @return value string or {@code null} if not found
	 */
	@SuppressWarnings("rawtypes")
	static String parseValue(Map properties, JdbcProperty property){
		if(properties !=null && null != property){
			if(property.isGlobal()){
				return (String) properties.get(property.key);
			}
			Boolean isDebug = "true".equalsIgnoreCase((String)properties.get(JdbcProperty.DEBUG));
			String prefix=isDebug ? "debug." : "work.";
			return (String) properties.get(property.withPrefix(prefix));
		}
		return null;
	}
	private static Properties getDatabaseproperties() {
		if(null == databaseProperties){
			synchronized (DataSourceConfig.class) {
				if(null == databaseProperties){
					databaseProperties = loadInitProperties(null);
				}
			}
		}
		return databaseProperties;
	}
	private static Properties asInitProps(Properties properties){
		String alias = parseValue(checkNotNull(properties,"databaseProperties is null"), JdbcProperty.ALIAS);
		if(alias != null){
			Properties initProps = loadInitProperties(alias);
			initProps.putAll(properties);
			return initProps;
		}else{
			return properties;
		}        
	}
	static DataSourceConfig  createConfig(Properties properties){
		properties = asInitProps(properties);
		return new DataSourceConfig(properties);
	}
	public static boolean isDebugOutput() {
		return debugOutput;
	}
	public static void setDebugOutput(boolean debugOutput) {
		DataSourceConfig.debugOutput = debugOutput;
	}
}